//=============================================================================
// ProMesh.NET - .NET Web Application Framework 
//
// Copyright (c) 2003-2008 Philippe Leybaert
//
// Permission is hereby granted, free of charge, to any person obtaining a copy 
// of this software and associated documentation files (the "Software"), to deal 
// in the Software without restriction, including without limitation the rights 
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is 
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//=============================================================================

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Activa.ProMesh
{
    internal class ControllerClass
    {
        private readonly Type _classType;
        private readonly List<MethodInfo> _setupMethods = new List<MethodInfo>();
        private readonly List<MethodInfo> _teardownMethods = new List<MethodInfo>();
        private readonly Dictionary<string, MethodInfo> _publicMethods = new Dictionary<string, MethodInfo>(StringComparer.InvariantCultureIgnoreCase);
        private readonly Dictionary<string, AjaxMethod> _ajaxMethods = new Dictionary<string, AjaxMethod>();
        private readonly Route[] _routes;
        private readonly FilterAttribute[] _filterAttributes;

        private readonly string _name;

        internal ControllerClass(Type classType)
        {
            List<Route> routes = new List<Route>();

            _classType = classType;
            _name = classType.Name;

            Type currentClassType = _classType;
            Stack<Type> pageTypeStack = new Stack<Type>();


            if (IsViewComponent)
            {
                ComponentNameAttribute[] nameAttributes = (ComponentNameAttribute[])classType.GetCustomAttributes(typeof(ComponentNameAttribute), false);

                if (nameAttributes.Length > 0)
                    _name = nameAttributes[0].Name;
            }

            UrlAttribute[] urlAttributes = (UrlAttribute[])classType.GetCustomAttributes(typeof(UrlAttribute), false);

            foreach (UrlAttribute urlAttribute in urlAttributes)
            {
                routes.Add(new Route(urlAttribute.Path, _name, urlAttribute.Action));
            }

            _filterAttributes = (FilterAttribute[])classType.GetCustomAttributes(typeof(FilterAttribute), false);

            Array.Sort(FilterAttributes, delegate(FilterAttribute att1, FilterAttribute att2) { return att1.Order - att2.Order; });

            while (currentClassType != typeof(Controller) && currentClassType != null)
            {
                pageTypeStack.Push(currentClassType);

                currentClassType = currentClassType.BaseType;
            }

            while (pageTypeStack.Count > 0)
            {
                currentClassType = pageTypeStack.Pop();

                MethodInfo[] methods = currentClassType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);

                foreach (MethodInfo methodInfo in methods)
                {
                    if (methodInfo.IsSpecialName)
                        continue;

                    if (methodInfo.IsDefined(typeof (BeforeActionAttribute), true))
                    {
                        _setupMethods.Add(methodInfo);
                    }
                    else if (methodInfo.IsDefined(typeof (AfterActionAttribute), true))
                    {
                        _teardownMethods.Add(methodInfo);
                    }
                    else if (methodInfo.IsDefined(typeof(AjaxAttribute), true))
                    {
                        AjaxAttribute ajaxAttribute = (AjaxAttribute) methodInfo.GetCustomAttributes(typeof (AjaxAttribute), true)[0];

                        if (IsViewComponent)
                        {
                            AjaxMethods[methodInfo.Name] = new AjaxMethod(methodInfo, ajaxAttribute);
                        }
                        else
                        {
                            AjaxMethods[methodInfo.Name] = new AjaxMethod(methodInfo, this, ajaxAttribute);
                        }
                    }
                    else if (methodInfo.IsPublic)
                    {
                        _publicMethods[methodInfo.Name] = methodInfo;

                        urlAttributes = (UrlAttribute[])methodInfo.GetCustomAttributes(typeof(UrlAttribute), false);

                        foreach (UrlAttribute urlAttr in urlAttributes)
                        {
                            routes.Add(new Route(urlAttr.Path,_name,methodInfo.Name));
                        }
                    }
                }
            }

            _routes = routes.ToArray();
        }

        internal Dictionary<string, AjaxMethod> AjaxMethods
        {
            get { return _ajaxMethods; }
        }

        internal string Name
        {
            get { return _name; }
        }

        internal Route[] Routes
        {
            get { return _routes; }
        }

        internal Controller CreateController()
        {
            Controller controller = (Controller) Activator.CreateInstance(_classType);

            controller.Initialize(this, null);

            return controller;
        }

        internal ViewComponent CreateViewComponent(TemplateParserContext context)
        {
            ViewComponent controller = (ViewComponent) Activator.CreateInstance(_classType);

            controller.Initialize(this, context);

            return controller;
        }

        internal bool IsViewComponent
        {
            get { return typeof (ViewComponent).IsAssignableFrom(_classType); }
        }

        internal FilterAttribute[] FilterAttributes
        {
            get { return _filterAttributes; }
        }

        internal string Run(ViewComponent viewComponent, TemplateParserContext context)
        {
            string returnedString = null;

            try
            {
                MethodInfo method = _publicMethods["Run"];

                object returnValue = method.Invoke(viewComponent, WebAppHelper.CreateParameters(method, context));

                if (returnValue == null)
                    returnedString = null;
                else if (method.ReturnType == typeof(string))
                    returnedString = (string) returnValue;
                else if (method.ReturnType == typeof(StringBuilder))
                    returnedString = returnValue.ToString();
            }
            catch (TargetInvocationException ex)
            {
                throw ExceptionHelper.ResolveTargetInvocationException(ex);
            }

            return returnedString;
            
        }

        internal bool HasActionMethod(string methodName)
        {
            return _publicMethods.ContainsKey(methodName);
        }

        //TODO: Merge with Run() for ViewComponent
        public void Run(Controller controller, string methodName)
        {
            try
            {
                MethodInfo method = _publicMethods[methodName];

                method.Invoke(controller, WebAppHelper.CreateParameters(method));
            }
            catch (TargetInvocationException ex)
            {
                throw ExceptionHelper.ResolveTargetInvocationException(ex);
            }

            
        }

        internal bool IsAjaxMethod(string methodName)
        {
            return _ajaxMethods.ContainsKey(methodName);
        }

        internal void RunAjaxMethod(string methodName)
        {
            Controller controller = null;

            AjaxMethod ajaxMethod = _ajaxMethods[methodName];

            if (!ajaxMethod.MethodInfo.IsStatic)
                controller = (Controller) Activator.CreateInstance(_classType);

            WebAppContext.Response.ContentType = ajaxMethod.ReturnXml ? "text/xml" : "application/json";

            WebAppContext.Response.Write(WebAppHelper.RunAjaxMethod(ajaxMethod.MethodInfo, controller, ajaxMethod.ReturnXml));
        }

        internal void RegisterAjaxMethods(Controller controller)
        {
            View view = (controller is ViewComponent) ? WebAppContext.RootView : controller.View;

            foreach (AjaxMethod ajaxMethod in _ajaxMethods.Values)
                ajaxMethod.Register(view);
        }

        internal void ExecutePreMethods(Controller controller, TemplateParserContext context)
        {
            try
            {
                foreach (MethodInfo method in _setupMethods)
                    method.Invoke(controller, WebAppHelper.CreateParameters(method, context));
            }
            catch (TargetInvocationException ex)
            {
                throw ExceptionHelper.ResolveTargetInvocationException(ex);
            }
        }

        internal void ExecutePostMethods(Controller controller, TemplateParserContext context)
        {
            try
            {
                foreach (MethodInfo method in _teardownMethods)
                    method.Invoke(controller, WebAppHelper.CreateParameters(method, context));
            }
            catch (TargetInvocationException ex)
            {
                throw ExceptionHelper.ResolveTargetInvocationException(ex);
            }
        }

    }
}
